
#include "diff.hpp"
#include <iostream>
#include <fstream>
#include <filesystem>
#include <vector>
#include <map>

void get_object_files(std::string obj_oid, std::map<std::string, DiffFile> &file_map,
                      bool lastCommitFlag) {
    std::ifstream inFile(obj_oid, std::ios::in);
    if (!inFile.is_open()) {
        std::cerr << "Error: Failed to open object file: " << obj_oid << std::endl;
        exit(1);
    }
    while (true) {
        ObjectType type = get_file_type(inFile);
        if (type == ObjectType::INVAILD_OBJECT) {
            break;
        }
        ObjectFile object_file;
        switch (type) {
        case ObjectType::TREE_OBJ:
            inFile >> object_file;
            if (object_file.oid != obj_oid) {
                get_object_files(OBJECT_DIR / object_file.oid, file_map, lastCommitFlag);
            }
            break;
        case ObjectType::BLOB_OBJ: {
            inFile >> object_file;
            if (file_map.find(object_file.file_name) == file_map.end()) {
                if (lastCommitFlag == false) {
                    // 当前的为最新的提交记录
                    file_map[object_file.file_name] = {object_file.file_name, object_file.oid, "",
                                                       DIFF_TYPE_ADD};
                } else {
                    // 当前的为上一个提交记录
                    file_map[object_file.file_name] = {object_file.file_name, "", object_file.oid,
                                                       DIFF_TYPE_DELETE};
                }
            } else {
                if (file_map[object_file.file_name].elem_one_oid == object_file.oid) {
                    file_map[object_file.file_name].diff_type = DIFF_TYPE_SAME;
                } else {
                    file_map[object_file.file_name].diff_type = DIFF_TYPE_MODIFY;
                }
                file_map[object_file.file_name].elem_two_oid = object_file.oid;
            };
        } break;

        case ObjectType::COMMIT_MESSAGE:
        case ObjectType::TAG_OBJ:
            std::cerr << "Error: Invalid object type: " << object_file.type << std::endl;
            exit(1);
            break;
        default:
            break;
        }
    }
    inFile.close();
    return;
}

std::map<std::string, DiffFile> diff_trees(std::string blob_id_one, std::string blob_id_two) {
    // 获取两个blob对应的目录树的区别
    std::map<std::string, DiffFile> file_map;
    get_object_files(OBJECT_DIR / blob_id_one, file_map, false);
    get_object_files(OBJECT_DIR / blob_id_two, file_map, true);
    return file_map;
};

std::map<std::string, DiffFile> diff_staged_tree(std::string blob_id_one) {
    // 获取当前staged区域和上一次commit的区别
    std::map<std::string, DiffFile> file_map;
    get_object_files(OBJECT_DIR / blob_id_one, file_map, false);
    for (auto &file : file_map) {
        file.second.diff_type = DIFF_TYPE_SAME;
    }
    std::ifstream inFile(INDEX_FILE, std::ios::in);
    while (true) {
        ObjectType type = get_file_type(inFile);
        if (type == ObjectType::INVAILD_OBJECT) {
            break;
        }
        ObjectFile object_file;
        switch (type) {
        case ObjectType::BLOB_OBJ: {
            inFile >> object_file;
            object_file.type = ObjectType::BLOB_OBJ;
            if (file_map.find(object_file.file_name) == file_map.end()) {
                file_map[object_file.file_name] = {object_file.file_name, "", object_file.oid,
                                                   DIFF_TYPE_ADD};
            } else {
                if (file_map[object_file.file_name].elem_one_oid != object_file.oid) {
                    file_map[object_file.file_name].diff_type = DIFF_TYPE_MODIFY;
                    swap(file_map[object_file.file_name].elem_one_oid,
                         file_map[object_file.file_name].elem_two_oid);
                }
            }
        } break;
        default:
            break;
        }
    }

    return file_map;
};

namespace fs = std::filesystem;
std::map<std::string, DiffFile> diff_current_tree(std::string blob_id_one) {
    // 获取当前目录树的区别，第一个obj_id为当前目录树的blob_id
    std::map<std::string, DiffFile> file_map;

    // 使用 recursive_directory_iterator 递归遍历目录
    for (const auto &entry : fs::recursive_directory_iterator(current_dir)) {
        // 输出每个条目的路径
        std::string file_path = entry.path().string();
        if (file_path.find(GIT_DIR) != std::string::npos) {
            continue;
        }
        if (entry.is_directory()) {
            continue;
        }

        if (file_path.find(current_dir / PROJECT_NAME) != std::string::npos) {
            continue;
        }

        ErrorMessage error_message = file_SHA1(file_path);
        if (error_message.code != ERROR_OK) {
            std::cerr << "Error: Failed to get SHA1 of file: " << file_path << std::endl;
            exit(1);
        } else {
            // 获取相对路径，作为map的key
            std::string relative_path =
                std::filesystem::relative(entry.path(), current_dir).string();

            if (file_map.find(relative_path) != file_map.end()) {
                std::cerr << "Error: File already exists: " << relative_path << std::endl;
                exit(1);
            }
            // 当前目录下有新文件
            file_map[relative_path] = {relative_path, error_message.message, "", DIFF_TYPE_ADD};
        }
    }
    get_object_files(OBJECT_DIR / blob_id_one, file_map, true);

    return file_map;
};

std::string run_linux_command(const std::string command) {
    FILE *pipe = popen(command.c_str(), "r");
    if (!pipe) {
        throw std::runtime_error("popen() failed!");
    }
    std::string output;
    const size_t bufferSize = 4096; // 使用更大的缓冲区
    char buffer[bufferSize];
    size_t bytesRead = 0;

    // 使用 fread 循环读取 diff 命令的输出
    while ((bytesRead = fread(buffer, 1, bufferSize, pipe)) > 0) {
        output.append(buffer, bytesRead);
    }

    pclose(pipe);
    return output;
}

std::string diff_current_files(const std::string file1, const std::string file2,
                               const std::string file1_path, const std::string file2_path) {
    std::string command = "bash -c \"diff -N --unified --show-c-function "
                          "--label a/" +
                          file1_path + " <(tail -n +2 " + file1 +
                          ") "
                          "--label b/" +
                          file2_path + " " + file2 + "\"";
    return run_linux_command(command);
}

void diff_merge_two_file(const std::string file1, const std::string file2,
                         const std::string output_file) {
    // 合并两个文件，无parent的版本
    std::string command =
        "bash -c \"diff -DHEAD " + file1 + " " + file2 + " >" + output_file + "\"";
    run_linux_command(command);
}

void diff_merge_two_file(const std::string file1, const std::string file2,
                         const std::string parent_file, const std::string output_file) {
    // 合并两个文件，有共同祖先的版本
    std::string command = "bash -c \"diff3 -m -L HEAD " + file1 + " -L BASE " + parent_file +
                          " -L MERGE_HEAD " + file2 + " >" + output_file + "\"";
    run_linux_command(command);
}

// diff_files 函数：对比两个文件，并返回 diff 输出，比较的是两个commit文件
std::string diff_files(const std::string file1, const std::string file2,
                       const std::string file1_path, const std::string file2_path) {
    // 构造 diff 命令：
    // --unified        生成统一格式的 diff 输出
    // --show-c-function 显示 C 代码中变化的函数名
    // --label 用于设置输出标签（如 a/blob 和 b/blob）
    std::string command = "diff  -N --unified --show-c-function "
                          "--label a/" +
                          file1_path + " " + file1 +
                          " "
                          "--label b/" +
                          file2_path + " " + file2;
    return run_linux_command(command);
}

void merge_two_commit(std::map<std::string, DiffFile> &file_map) {
    for (auto &file : file_map) {
        switch (file.second.diff_type) {
        case DIFF_TYPE_SAME:

            break;
        case DIFF_TYPE_ADD:
        case DIFF_TYPE_DELETE:
        case DIFF_TYPE_MODIFY:
            if (file.second.parent_oid.empty() || file.second.elem_one_oid.empty() ||
                file.second.elem_two_oid.empty()) {
                diff_merge_two_file(OBJECT_DIR / file.second.elem_one_oid,
                                    OBJECT_DIR / file.second.elem_two_oid, file.first);
            } else {
                diff_merge_two_file(OBJECT_DIR / file.second.elem_one_oid,
                                    OBJECT_DIR / file.second.elem_two_oid,
                                    OBJECT_DIR / file.second.parent_oid, file.first);
            }

            break;
        default:
            break;
        }
    }
};

std::set<std::string> get_commit_ancestors(std::string commit_oid) {
    std::set<std::string> ancestors{commit_oid};
    std::string current_commit = commit_oid;
    while (true) {
        CommitMessage commitMessage = get_commit_message(current_commit);
        ancestors.insert(commitMessage.parent_oid_vct.front());
        if (commitMessage.parent_oid_vct.front() == DEFAULT_PARENT_OID) {
            break;
        }
        current_commit = commitMessage.parent_oid_vct.front();
    };
    return ancestors;
}

std::string get_common_ancestor(std::string commit_one, std::string commit_two) {
    // 获取两个commit的共同祖先
    if (commit_one == commit_two) {
        return commit_one;
    }
    auto ancestors             = get_commit_ancestors(commit_one);
    std::string current_commit = commit_two;
    while (true) {
        CommitMessage commitMessage = get_commit_message(current_commit);
        if (ancestors.find(commitMessage.parent_oid_vct.front()) != ancestors.end()) {
            return commitMessage.parent_oid_vct.front();
        }
        if (commitMessage.parent_oid_vct.front() == DEFAULT_PARENT_OID) {
            break;
        }
        current_commit = commitMessage.parent_oid_vct.front();
    }
    std::cerr << "Error: Failed to find common ancestor of commit: " << commit_one << " and "
              << commit_two << std::endl;
    exit(1);
    return DEFAULT_PARENT_OID;
};

void diff_build_parent_oid(std::map<std::string, DiffFile> &file_map, std::string parent_obj_oid) {
    std::ifstream inFile(OBJECT_DIR / parent_obj_oid, std::ios::in);
    if (!inFile.is_open()) {
        std::cerr << "Error: Failed to open object file: " << parent_obj_oid << std::endl;
        exit(1);
    }
    while (true) {
        ObjectType type = get_file_type(inFile);
        if (type == ObjectType::INVAILD_OBJECT) {
            break;
        }
        ObjectFile object_file;
        switch (type) {
        case ObjectType::TREE_OBJ:
            inFile >> object_file;
            if (object_file.oid != parent_obj_oid) {
                diff_build_parent_oid(file_map, object_file.oid);
            }
            break;
        case ObjectType::BLOB_OBJ: {
            inFile >> object_file;
            if (file_map.find(object_file.file_name) != file_map.end()) {
                file_map[object_file.file_name].parent_oid = parent_obj_oid;
            } else {
                std::cerr << "Error: Failed to find file: " << object_file.file_name << std::endl;
                exit(1);
            }
        } break;
        case ObjectType::COMMIT_MESSAGE:
        case ObjectType::TAG_OBJ:
            std::cerr << "Error: Invalid object type: " << object_file.type << std::endl;
            exit(1);
            break;
        default:
            break;
        }
    }
    inFile.close();
    return;
};